-----------------------------------------------------------------------------
-- URI parsing, composition and relative URL resolution
-- Adapted from LuaSocket 1.4 toolkit.
-- Author:  Josh Kewley
-- Company: Insario Corporation
--          Scottsdale, AZ
-- Date: 03/27/2008
----------------------------------------------------------------------------
module(...,package.seeall)

RevisionNumber = "0.61"

local function DB(message)
    debug("Url", message)
end

-----------------------------------------------------------------------------
-- Parses a url and returns a table with all its parts according to RFC 2396
-- The following grammar describes the names given to the URL parts
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
-- <authority> ::= <userinfo>@<host>:<port>
-- <userinfo> ::= <user>[:<password>]
-- <path> :: = {<segment>/}<segment>
-- Input
--   url: uniform resource locator of request
--   default: table with default values for each field
-- Returns
--   table with the following fields, where RFC naming conventions have
--   been preserved:
--     scheme, authority, userinfo, user, password, host, port,
--     path, params, query, fragment
-- Obs:
--   the leading '/' in {/<path>} is considered part of <path>
-----------------------------------------------------------------------------
function parse_url(url, default)
    DB("parsing url")
    -- initialize default parameters
    local parsed = default or {}
    -- empty url is parsed to nil
    if not url or url == "" then return nil end
    --default values
    parsed.port = "80"
    parsed.scheme="http"

    DB("url: "..url)
    -- remove whitespace
    url = string.gsub(url, "%s", "")
    -- get fragment
    url = string.gsub(url, "#(.*)$", function(f) parsed.fragment = f  return "" end)
    --parsed.fragment = string.match(url, "#(.*)$")
    if (parsed.fragment) then DB("fragment: "..parsed.fragment) end
    -- get scheme
    url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) parsed.scheme = s return "" end)
    --debug("URL", url)
    --parsed.scheme = string.match(url, "^([%w][%w%+%-%.]*)%:")
    if (parsed.scheme) then DB("scheme: "..parsed.scheme) end
    -- get authority
    url = string.gsub(url, "^//([^/]*)", function(n) parsed.authority = n  return "" end)
    --parsed.authority = string.match(url,"//([^/]*)")
    if (parsed.authority) then DB("authority: "..parsed.authority) end
    -- get query string
    url = string.gsub(url, "%?(.*)", function(q) parsed.query = q  return "" end)
    --parsed.query = string.match(url,"%?(.*)")
    if (parsed.query) then DB("query: "..parsed.query) end
    -- get params
    url = string.gsub(url, "%;(.*)", function(p) parsed.params = p  return "" end)
    --parsed.params = string.match(url,"%;(.*)")
    if (parsed.params) then DB("params: "..parsed.params) end
    if url ~= "" then parsed.path = url end
    if (parsed.path) then DB("path: "..parsed.path) end

    local authority = parsed.authority
    if not authority then
        DB("returning without user info")
        return parsed
    end
    authority = string.gsub(authority,"^([^@]*)@",function(u) parsed.userinfo = u return "" end)
    if (parsed.userinfo) then DB("userinfo:"..parsed.userinfo) end
    authority = string.gsub(authority, ":([^:]*)$", function(p) parsed.port = p return "" end)
    if (parsed.port) then DB("port:"..parsed.port)  end
    if authority ~= "" then parsed.host = authority end
    if (parsed.host) then DB("host:"..parsed.host) end
    local userinfo = parsed.userinfo
    if not userinfo then return parsed end
    userinfo = string.gsub(userinfo, ":([^:]*)$", function(p) parsed.password = p return "" end)
    if (parsed.password) then DB("password:"..parsed.password) end
    parsed.user = userinfo
    if (parsed.user) then DB("user:"..parsed.user) end
    DB("returning with user info")

    return parsed
end

-----------------------------------------------------------------------------
-- Rebuilds a parsed URL from its components.
-- Components are protected if any reserved or unallowed characters are found
-- Input
--   parsed: parsed URL, as returned by parse
-- Returns
--   a string with the corresponding URL
-----------------------------------------------------------------------------
function build_url(parsed)
    local url = parsed.path or ""
    if parsed.params then url = url .. ";" .. parsed.params end
    if parsed.query then url = url .. "?" .. parsed.query end
	local authority = parsed.authority
	if parsed.host then
		authority = parsed.host
		if parsed.port then authority = authority .. ":" .. parsed.port end
		local userinfo = parsed.userinfo
		if parsed.user then
			userinfo = parsed.user
			if parsed.password then
				userinfo = userinfo .. ":" .. parsed.password
			end
		end
		if userinfo then authority = userinfo .. "@" .. authority end
	end
    if authority then url = "//" .. authority .. url end
    if parsed.scheme then url = parsed.scheme .. ":" .. url end
    if parsed.fragment then url = url .. "#" .. parsed.fragment end
    url = string.gsub(url, "%s", "")
    return url
end

-----------------------------------------------------------------------------
-- Builds a absolute URL from a base and a relative URL according to RFC 2396
-- Input
--   base_url
--   relative_url
-- Returns
--   corresponding absolute url
-----------------------------------------------------------------------------
function absolute_url(base_url, relative_url)
    local base = parse_url(base_url)
    local relative = parse_url(relative_url)
    if not base then return relative_url
    elseif not relative then return base_url
    elseif relative.scheme then return relative_url
    else
        relative.scheme = base.scheme
        if not relative.authority then
            relative.authority = base.authority
            if not relative.path then
                relative.path = base.path
                if not relative.params then
                    relative.params = base.params
                    if not relative.query then
                        relative.query = base.query
                    end
                end
            else
                relative.path = absolute_path(base.path,relative.path)
            end
        end
        return build_url(relative)
    end
end

-----------------------------------------------------------------------------
-- Breaks a path into its segments, unescaping the segments
-- Input
--   path
-- Returns
--   segment: a table with one entry per segment
-----------------------------------------------------------------------------
function parse_path(path)
	local parsed = {}
	path = path or ""
	path = string.gsub(path, "%s", "")
	string.gsub(path, "([^/]+)", function (s) tinsert(parsed, s) end)
	for i = 1, getn(parsed) do
		parsed[i] = unescape(parsed[i])
	end
	if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
	if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
	return parsed
end

-----------------------------------------------------------------------------
-- Builds a path component from its segments, escaping protected characters.
-- Input
--   parsed: path segments
--   unsafe: if true, segments are not protected before path is built
-- Returns
--   path: correspondin path string
-----------------------------------------------------------------------------
function build_path(parsed, unsafe)
	local path = ""
	local n = getn(parsed)
	if unsafe then
		for i = 1, n-1 do
			path = path .. parsed[i]
			path = path .. "/"
		end
		if n > 0 then
			path = path .. parsed[n]
			if parsed.is_directory then path = path .. "/" end
		end
	else
		for i = 1, n-1 do
			path = path .. protect_segment(parsed[i])
			path = path .. "/"
		end
		if n > 0 then
			path = path .. protect_segment(parsed[n])
			if parsed.is_directory then path = path .. "/" end
		end
	end
	if parsed.is_absolute then path = "/" .. path end
	return path
end

function make_set(aTable)
	local s = {}
	for i = 1, #aTable do
		s[aTable[i]] = 1
	end
	return s
end

-- these are allowed withing a path segment, along with alphanum
-- other characters must be escaped
segment_set = make_set {
    "-", "_", ".", "!", "~", "*", "'", "(",
	")", ":", "@", "&", "=", "+", "$", ",",
}

function protect_segment(s)
	local segment_set = segment_set
	return string.gsub(s, "(%W)", function (c)
		if segment_set[c] then return c
		else return escape(c) end
	end)
end

-----------------------------------------------------------------------------
-- Builds a path from a base path and a relative path
-- Input
--   base_path
--   relative_path
-- Returns
--   corresponding absolute path
-----------------------------------------------------------------------------
function absolute_path(base_path, relative_path)
    if string.sub(relative_path, 1, 1) == "/" then return relative_path end
    local path = string.gsub(base_path, "[^/]*$", "")
    path = path .. relative_path
    path = string.gsub(path, "([^/]*%./)", function (s)
        if s ~= "./" then return s else return "" end
    end)
    path = string.gsub(path, "/%.$", "/")
    local reduced
    while reduced ~= path do
        reduced = path
        path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
            if s ~= "../../" then return "" else return s end
        end)
    end
    path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
        if s ~= "../.." then return "" else return s end
    end)
    return path
end
--[[
function debug(cat, msg)
    print(string.format("%s      %s", cat, msg))
end
]]



-----------------------------------------------------------------------------
-- Direct and inverse convertion tables for base64
-----------------------------------------------------------------------------
t64 = {[00] = 'A', [01] = 'B', [02] = 'C', [03] = 'D', [04] = 'E', [05] = 'F', [06] = 'G', [07] = 'H', [08] = 'I', [09] = 'J', [10] = 'K', [11] = 'L', [12] = 'M', [13] = 'N', [14] = 'O', [15] = 'P', [16] = 'Q', [17] = 'R', [18] = 'S', [19] = 'T', [20] = 'U', [21] = 'V', [22] = 'W', [23] = 'X', [24] = 'Y', [25] = 'Z', [26] = 'a', [27] = 'b', [28] = 'c', [29] = 'd', [30] = 'e', [31] = 'f', [32] = 'g', [33] = 'h', [34] = 'i', [35] = 'j', [36] = 'k', [37] = 'l', [38] = 'm', [39] = 'n', [40] = 'o', [41] = 'p', [42] = 'q', [43] = 'r', [44] = 's', [45] = 't', [46] = 'u', [47] = 'v', [48] = 'w', [49] = 'x', [50] = 'y', [51] = 'z', [52] = '0', [53] = '1', [54] = '2', [55] = '3', [56] = '4', [57] = '5', [58] = '6', [59] = '7', [60] = '8', [61] = '9', [62] = '+', [63] = '/', [64] = '='}
f64 = {['A'] = 00, ['B'] = 01, ['C'] = 02, ['D'] = 03, ['E'] = 04, ['F'] = 05, ['G'] = 06, ['H'] = 07, ['I'] = 08, ['J'] = 09, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63, ['='] = 64}

function mod(aNumber, aBase)
    return aNumber % aBase
end
-----------------------------------------------------------------------------------------------------
-- Converts a three byte sequence into its four character base64 representation
-----------------------------------------------------------------------------------------------------
function t2f(a,b,c)
	local s = string.byte(a)*65536 + string.byte(b)*256 + string.byte(c)
	local ca, cb, cc, cd
	cd = mod(s, 64)
	s = (s - cd) / 64
	cc = mod(s, 64)
	s = (s - cc) / 64
	cb = mod(s, 64)
	ca = (s - cb) / 64
	return t64[ca] .. t64[cb] .. t64[cc] .. t64[cd]
end

-----------------------------------------------------------------------------------------------------
-- Converts a four character base64 representation into its three byte sequence
-----------------------------------------------------------------------------------------------------
function f2t(a,b,c,d)
	local s = f64[a]*262144 + f64[b]*4096 + f64[c]*64 + f64[d]
	local ca, cb, cc
	cc = mod(s, 256)
	s = (s - cc) / 256
	cb = mod(s, 256)
	ca = (s - cb) / 256
	return string.char(ca, cb, cc)
end

-----------------------------------------------------------------------------
-- Creates a base64 representation of an incomplete last block
-----------------------------------------------------------------------------
function to64pad(s)
	local a, b, ca, cb, cc, _
	_, _, a, b = string.find(s, "(.?)(.?)")
	if b == "" then
		s = string.byte(a)*16
		cb = mod(s, 64)
		ca = (s - cb)/64
		return t64[ca] .. t64[cb] .. "=="
	end
	s = string.byte(a)*1024 + string.byte(b)*4
	cc = mod(s, 64)
	s = (s - cc) / 64
	cb = mod(s, 64)
	ca = (s - cb)/64
	return t64[ca] .. t64[cb] .. t64[cc] .. "="
end

-----------------------------------------------------------------------------------
-- Decodes the base64 representation of an incomplete last block
-----------------------------------------------------------------------------------
function from64pad(s)
	local a, b, c, d
	local ca, cb, _
	_, _, a, b, c, d = string.find(s, "(.)(.)(.)(.)")
	if d ~= "=" then return f2t(a,b,c,d)
	elseif c ~= "=" then
		s = f64[a]*1024 + f64[b]*16 + f64[c]/4
		cb = mod(s, 256)
		ca = (s - cb)/256
		return string.char(ca, cb)
	else
		s = f64[a]*4 + f64[b]/16
		ca = mod(s, 256)
		return string.char(ca)
	end
end

-----------------------------------------------------------------------------
-- Break a string in lines of equal size
-- Input
--   s: string to be broken encoded
--   w: width of output string lines
-- Returns
--   string broken in lines
-----------------------------------------------------------------------------
function split(s, w)
	-- this looks ugly,  but for lines with less  then 200 columns,
	-- it is more efficient then using string.sub and the concat module
	local l = "(" .. string.rep(".", w) .. ")"
	return string.gsub(s, l, "%1\r\n")
end

-----------------------------------------------------------------------------
-- Encodes a string into its base64 representation
-- Input
--   s: binary string to be encoded
--   single: single line output?
-- Returns
--   string with corresponding base64 representation
-----------------------------------------------------------------------------
function base64(s, single)
	local pad, whole
	local l = string.len(s)
	local m = mod(l, 3)
	l = l - m
	if l > 0 then whole = string.gsub(string.sub(s, 1, l), "(.)(.)(.)", t2f)
	else whole = "" end
	if m > 0 then pad = to64pad(string.sub(s, l+1))
	else pad = "" end
	if single then return whole .. pad
	else return split(whole .. pad, 76) end
end

-----------------------------------------------------------------------------
-- Decodes a string from its base64 representation
-- Input
--   s: base64 string
-- Returns
--   decoded binary string
-----------------------------------------------------------------------------
function unbase64(s)
    -- clean string
	local f64 = f64
	s = string.gsub(s, "(.)", function (c)
		if f64[c] then return c
		else return "" end
	end)
	local l = string.len(s)
	local whole, pad
	if l > 4 then whole = string.gsub(string.sub(s, 1, -5), "(.)(.)(.)(.)", f2t)
	else whole = "" end
	pad = from64pad(string.sub(s, -4))
	return whole .. pad
end

-----------------------------------------------------------------------------
-- Encodes a string into its hexadecimal representation
-- Input
--   s: binary string to be encoded
-- Returns
--   string with corresponding hexadecimal representation
-----------------------------------------------------------------------------
function hexa(s)
	return string.gsub(s, "(.)", function(c)
		return format("%02x", string.byte(c))
	end)
end

-----------------------------------------------------------------------------
-- Encodes a string into its hexadecimal representation
-- Input
--   s: hexa string
-- Returns
--   decoded binary string
-----------------------------------------------------------------------------
function unhexa(s)
	return string.gsub(s, "(.)(.)", function(d,u)
		return string.char(tonumber(d*16 + u))
	end)
end

-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
--   s: binary string to be encoded
-- Returns
--   escaped representation of string binary
-----------------------------------------------------------------------------
function unescape(aString)
    aString = string.gsub(aString, "+", " ")
    aString = string.gsub(aString, "%%(%x%x)", function(hex) return string.char(tonumber(hex, 16)) end)
    return aString
end

-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
--   s: binary string to be encoded
-- Returns
--   escaped representation of string binary
-----------------------------------------------------------------------------
function escape(s)
	return string.gsub(s, "(.)", function(c)
		return format("%%%02x", strbyte(c))
	end)
end

-- converts a string in querystring format to a hashtable where table[name]=value
function QuerystringToTable(queryString)
    DB("Querystring to table")
    statusTable={}
    for name, value in string.gmatch(queryString, "([^&=]+)=([^&=]+)") do
        name = unescape(name)
        value = unescape(value)
        DB(" -->"..name.." : "..value)
        statusTable[name]=value
    end
    return statusTable
end
